package com.itaming.lycheeframework.jackson.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;

import java.io.IOException;
import java.math.BigInteger;

/**
 * 大数值序列化，避免超过<code>JavaScript</code>的精度，造成精度丢失
 *
 * @author A.Ming
 */
@JacksonStdImpl
public class JsSafeIntegerSerializer extends NumberSerializer {

    /**
     * 序列化器实例
     */
    public static final JsSafeIntegerSerializer INSTANCE = new JsSafeIntegerSerializer(Number.class);

    /**
     * <code>JavaScript</code>最大安全整数：<code>Math.pow(2, 53) - 1</code>
     * <p>
     * 参考：<code>JavaScript</code>中的<code>Number.MAX_SAFE_INTEGER</code>
     */
    private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;

    /**
     * <code>JavaScript</code>最小安全整数：<code>-(Math.pow(2, 53) - 1)</code>
     * <p>
     * 参考：<code>JavaScript</code>中的<code>Number.MIN_SAFE_INTEGER</code>
     */
    private static final long JS_MIN_SAFE_INTEGER = -JS_MAX_SAFE_INTEGER;

    public JsSafeIntegerSerializer(Class<? extends Number> rawType) {
        super(rawType);
    }

    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (isOutOfJsSafeIntegerRange(value)) {
            gen.writeString(value.toString());
        } else {
            super.serialize(value, gen, provider);
        }
    }

    /**
     * 判断数值是否超出了<code>JavaScript</code>安全整数范围
     *
     * @param value 数值
     * @return boolean
     */
    private boolean isOutOfJsSafeIntegerRange(Number value) {
        // BigInteger 类型可能会比long更大，单独处理
        if (value instanceof BigInteger bigIntValue) {
            return bigIntValue.compareTo(BigInteger.valueOf(JS_MIN_SAFE_INTEGER)) < 0 ||
                bigIntValue.compareTo(BigInteger.valueOf(JS_MAX_SAFE_INTEGER)) > 0;
        }

        // 此序列化器只针对 Long 和 BigInteger 类型
        // 所以，这里统一以long来处理
        long longValue = value.longValue();
        return longValue < JS_MIN_SAFE_INTEGER || longValue > JS_MAX_SAFE_INTEGER;
    }

}
